﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace LightingPlatform.Communication
{
    public class TCPClient
    {
        private const byte Head = 0xAA;
        private const byte Tail = 0xFF;
        private Socket _clientSocket;
        private EndPoint _endPoint;

        public bool IsConnected
        {
            get;
            set;
        }

        private SocketAsyncEventArgs _sendParameter;

        public SocketAsyncEventArgs SendParameter
        {
            get
            {
                if (_sendParameter == null)
                {
                    _sendParameter = new SocketAsyncEventArgs();
                    _sendParameter.RemoteEndPoint = _endPoint;
                }
                return _sendParameter;
            }
        }

        private SocketAsyncEventArgs _receiveParameter;

        public SocketAsyncEventArgs ReceiveParameter
        {
            get
            {
                if (_receiveParameter == null)
                {
                    _receiveParameter = new SocketAsyncEventArgs();
                }
                return _receiveParameter;
            }
        }

        private TaskCompletionSource<bool> _connectTask;
        private byte[] _receiveBuffer;



        public TCPClient(EndPoint point)
        {
            SendQueue = new Queue<byte[]>();
            _callDictionary = new Dictionary<int, TaskCompletionSource<byte[]>>();
            _clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _endPoint = point;
        }

        public async Task<bool> ConnectAsync()
        {
            SendParameter.Completed += ConnectCompleted;
            if (!_clientSocket.ConnectAsync(this.SendParameter))
            {
                this.ConnectCompleted(_clientSocket, this.SendParameter);
            }

            _connectTask = new TaskCompletionSource<bool>();
            return await this._connectTask.Task;
        }

        public Queue<byte[]> SendQueue
        {
            get;
            private set;
        }

        public bool Send(byte[] buffer)
        {
            if (!IsConnected)
            {
                return false;
            }

            _sendParameter = null;
            SendParameter.SetBuffer(buffer, 0, buffer.Length);
            return _clientSocket.SendAsync(this.SendParameter);
        }


        public void ConnectCompleted(object sender, SocketAsyncEventArgs e)
        {
            e.Completed -= this.ConnectCompleted;

            if (e.SocketError == SocketError.Success)
            {
                IsConnected = true;

                ReceiveParameter.Completed -= ReceiveCompleted;
                ReceiveParameter.Completed += ReceiveCompleted;
                
                Receive();

                _connectTask.SetResult(true);
                _connectTask = null;
            }
            else
            {
                _connectTask.SetException(new SocketException((int)e.SocketError));
                _connectTask = null;
            }
        }

        private void Receive()
        {
            _receiveBuffer = new byte[_clientSocket.ReceiveBufferSize];
            ReceiveParameter.SetBuffer(this._receiveBuffer, 0, this._receiveBuffer.Length);

            if (!_clientSocket.ReceiveAsync(ReceiveParameter))
            {
                this.ReceiveCompleted(this._clientSocket, this.ReceiveParameter);
            }
        }

        private static int _sendNumber;
        private MemoryStream _stream;
        private Dictionary<int, TaskCompletionSource<byte[]>> _callDictionary;

        private void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
            {
                byte[] data = new byte[e.BytesTransferred];
                Array.Copy(this._receiveBuffer, 0, data, 0, e.BytesTransferred);

                for (int i = 0; i < data.Length; i++)
                {
                    byte value = data[i];

                    switch (value)
                    {
                        case Head:
                            if (i == 0)
                            {
                                _stream = new MemoryStream();
                            }
                            else
                            {
                                if (this._stream != null)
                                {
                                    this._stream.WriteByte(value);
                                }
                            }
                            break;
                        case Tail:
                            if (i == data.Length - 1)
                            {
                                _stream.Position = 0;
                                //Debug.WriteLine(Utility.ByteArrayToHexString(this.Stream.ToArray()));
                                Unpack(this._stream);
                                _stream = null;
                            }
                            break;
                        default:
                            if (this._stream != null)
                            {
                                this._stream.WriteByte(value);
                            }
                            break;
                    }
                }
            }
            else
            {
                if (IsConnected)
                {
                    Close();
                }
            }
                
        }

        private int Pack(ushort command, byte[] data)
        {
            _sendNumber++;
            this.Pack(command, _sendNumber, 0, data);
            return _sendNumber;
        }

        private void Pack(ushort commandId, int sendNumber, int receiveNumber, byte[] data)
        {
            byte[] header = new byte[10];
            header[0] = (byte)(commandId >> 8);
            header[1] = (byte)(commandId >> 0);
            header[2] = (byte)(sendNumber >> 24);
            header[3] = (byte)(sendNumber >> 16);
            header[4] = (byte)(sendNumber >> 8);
            header[5] = (byte)(sendNumber >> 0);
            header[6] = (byte)(receiveNumber >> 24);
            header[7] = (byte)(receiveNumber >> 16);
            header[8] = (byte)(receiveNumber >> 8);

            header[9] = (byte)(receiveNumber >> 0);

            MemoryStream stream = new MemoryStream();
            stream.WriteByte(Head);
            stream.Write(header, 0, header.Length);

            if (data != null && data.Length > 0)
            {
                stream.Write(data, 0, data.Length);
            }

            stream.WriteByte(Tail);
            Send(stream.ToArray());
        }

        public void Close()
        {
            _clientSocket.Shutdown(SocketShutdown.Both);
            _clientSocket.Close();
            IsConnected = false;
        }

        private int Unpack(MemoryStream stream)
        {
            ushort commandId;
            int sendNumber;
            int receiveNumber;
            byte[] data;

            Unpack(stream, out commandId, out sendNumber, out receiveNumber, out data);
            Debug.WriteLine("Unpack:CommandId:{0},ReceiveNumber:{1}", commandId, receiveNumber);

            TaskCompletionSource<byte[]> tcs;
            if (_callDictionary.TryGetValue(receiveNumber, out tcs))
            {
                _callDictionary.Remove(receiveNumber);
                tcs.SetResult(data);
            }
            return sendNumber;
        }

        private void Unpack(MemoryStream stream, out ushort commandId, out int sendNumber, out int receiveNumber, out byte[] data)
        {
            byte header0 = (byte)stream.ReadByte();
            byte header1 = (byte)stream.ReadByte();
            commandId = (ushort)((header0 << 8) + header1);

            byte header2 = (byte)stream.ReadByte();
            byte header3 = (byte)stream.ReadByte();
            byte header4 = (byte)stream.ReadByte();
            byte header5 = (byte)stream.ReadByte();
            sendNumber = (header2 << 24) + (header3 << 16) + (header4 << 8) + (header5 << 0);

            byte header6 = (byte)stream.ReadByte();
            byte header7 = (byte)stream.ReadByte();
            byte header8 = (byte)stream.ReadByte();
            byte header9 = (byte)stream.ReadByte();
            receiveNumber = (header6 << 24) + (header7 << 16) + (header8 << 8) + header9;

            List<byte> list = new List<byte>();
            while (stream.Position < stream.Length)
            {
                list.Add((byte)stream.ReadByte());
            }

            data = list.ToArray();
        }

        public async Task<byte[]> Call(CommandType command, byte[] data = null)
        {
            var number = Pack((ushort)command, data);
            var tcs = new TaskCompletionSource<byte[]>();
            _callDictionary[number] = tcs;

            return await tcs.Task;
        }
    }
}
